#include <kaleidoscope/frontend/semantic_analysis/scope.hpp>
//=====================================================================
#include <kaleidoscope/common/symbol_ids.hpp>
#include <kaleidoscope/frontend/syntactical_analysis/marshall.hpp>
#include <kaleidoscope/frontend/semantic_analysis/resolve.hpp>
//=====================================================================
using namespace atma::sooty;
using namespace kal;
//=====================================================================
#include <boost/spirit/home/phoenix/core.hpp> // omg



/*

parseme_container kal::scope::find_value(scopeme_ptr_ref scope, const_parseme_ptr_ref node, kal::resolve::closeness::Enum min_closeness)
{
	parseme_container results;
	
	while (scope)
	{
		if (node->id == ids::function_call)
		{
			parseme_container R = function::find(scope, node, min_closeness);
			results.insert(results.end(), R.begin(), R.end());
		}
		
		if (scope->parent().expired()) break;
		scope = scopeme_ptr(scope->parent());
	}
	
	return results;
}

parseme_container kal::scope::function::find(scopeme_ptr_ref scope, atma::sooty::const_parseme_ptr_ref node, kal::resolve::closeness::Enum min_closeness)
{
	scopeme::bindings_t results = scope->find_binding( marshall::name(node)->value.string );
	
	parseme_container possibilities;
	
	// - we now have a list of symbols that matched the name of the function call.
	// - now we must iterate through them and match them
	for (scopeme::bindings_t::iterator i = results.begin(); i != results.end(); ++i)
	{
		parseme_ptr_ref function_candidate = *i;
		if (function_candidate->id == ids::function)
		{
			// got a function with the same name! now we check arguments.
			resolve::closeness::Enum closeness = resolve::types(node, function_candidate, scope);
			
			// SERIOUSLY
			//   - in the future, we'll need to add the ability to cast:
			// if (closeness >= min_closeness)
			if (closeness == resolve::closeness::perfect)
			{
				possibilities.push_back(function_candidate);
			}
		}
	}	
	
	return possibilities;
}



void scope::detail::build::start_rule(scopeme_ptr_ref scope, atma::sooty::const_parseme_ptr_ref ast)
{
	for (parseme_container::const_iterator i = ast->children.begin(); i != ast->children.end(); ++i)
	{
		if ((*i)->id == ids::function)
		{
			scope->add_value_binding(*i);
			scopeme_ptr function_scope = scope->child_scope(*i);
			scope::detail::build::function(function_scope, *i);
		}
	}
}

void scope::detail::build::function(scopeme_ptr_ref scope, atma::sooty::const_parseme_ptr_ref ast)
{
	const_parseme_ptr_ref pl = marshall::function::parameter_list(ast);
	
	// parameters!
	for (parseme_container::iterator i = pl->children.begin(); i != pl->children.end(); ++i)
	{
		scope->add_value_binding(*i);
	}
	
	// and variables (to be added...)
}


void scope::build(scopeme_ptr_ref scope_root, atma::sooty::const_parseme_ptr_ref ast_root)
{
	scope::detail::build::start_rule(scope_root, ast_root);
}

*/

size_t type_of(const_parseme_ptr_ref N)
{
	switch (N->id)
	{
		// for function calls, we have to lookup the function in question, and use
		// the return-type. we have very simple error checking here, because the
		// semantic analysis part of the program will take care of any ambiguities
		// or lack of matching functions
		case ids::function_call:
		{
			parseme_container functions;
			scope::upwards_copy_if(N, std::back_inserter(functions), scope::function_matches_call(N));
			if (functions.empty() || functions.size() > 1) return ids::undefined;
			return marshall::function::return_type(functions.front())->id;
		}
		
		// handle all other things very similarly
		default:
			return marshall::type(N)->id;
	}
}

bool scope::types_match(atma::sooty::const_parseme_ptr_ref lhs, atma::sooty::const_parseme_ptr_ref rhs)
{
	return type_of(lhs) == type_of(rhs);
}









kal::scope::function_matches_call::function_matches_call( atma::sooty::const_parseme_ptr_ref call )
	: call_(call)
{
	ATMA_ASSERT(call_->id == kal::ids::function_call);
}

bool kal::scope::function_matches_call::operator()( atma::sooty::const_parseme_ptr_ref candidate )
{
	using atma::sooty::const_parseme_ptr_ref;
	using atma::sooty::parseme_container;

	if (candidate->id != ids::function) return false;
	if (marshall::name(call_)->value.string != marshall::name(candidate)->value.string) return false;

	parseme_container& call_pl = marshall::parameter_list(call_)->children;
	parseme_container& candidate_pl = marshall::parameter_list(candidate)->children;

	if (call_pl.size() != candidate_pl.size()) return false;

	parseme_container::const_iterator argument = call_pl.begin();
	parseme_container::const_iterator parameter = candidate_pl.begin();

	bool good = true;
	for ( ; argument != call_pl.end() && parameter != candidate_pl.end(); ++argument, ++parameter)
	{
		if (!kal::scope::types_match(*argument, *parameter))
			good = false;
	}

	return good;
}


kal::scope::other_functions_matching::other_functions_matching( atma::sooty::const_parseme_ptr_ref function )
	: function_(function)
{

}

bool kal::scope::other_functions_matching::operator()( atma::sooty::const_parseme_ptr_ref candidate )
{
	using atma::sooty::const_parseme_ptr_ref;
	using atma::sooty::parseme_container;

	if (candidate->id != ids::function) return false;
	if (*candidate == *function_) return false;
	
	if (marshall::name(function_)->value.string != marshall::name(candidate)->value.string) return false;

	parseme_container& function_pl = marshall::parameter_list(function_)->children;
	parseme_container& candidate_pl = marshall::parameter_list(candidate)->children;

	if (function_pl.size() != candidate_pl.size()) return false;

	parseme_container::const_iterator argument = function_pl.begin();
	parseme_container::const_iterator parameter = candidate_pl.begin();

	bool good = true;
	for ( ; argument != function_pl.end() && parameter != candidate_pl.end(); ++argument, ++parameter)
	{
		if (!kal::scope::types_match(*argument, *parameter))
			good = false;
	}

	return good;
}